/*
 * Famon (http://code.google.com/p/famon/)
 * Copyright (c) 2010 Carl Crous.
 *
 * This file is part of Famon.
 *
 * Famon is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Famon is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Famon.  If not, see <http://www.gnu.org/licenses/>.
 */

package za.org.foobar.famon.frontend.client.framework;

import za.org.foobar.famon.frontend.client.util.ClientUtil;

import java.util.Date;
/**
 * Utitlity methods for dates since we dont have SimpleDateFormat or
 * Calendar on the client. <b>This code must be able to run on the client
 * and server</b>.
 */
public class DateUtil {

    private DateUtil() {
    }

    /** Number of millisecs in one day. **/
    public static final int DAY_MS = 60 * 60 * 24 * 1000;

    /** Number of days in each month in a non-leap year. **/
    public static final int[] MONTH_DAYS =
        new int[]{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    /** 3 character names of each day. */
    public static final String[] DAY_NAMES =
            new String[]{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};

    /** Full name of each day. */
    public static String[] DAY_NAMES_FULL = new String[]{"Monday", "Tuesday",
            "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};

    /** 3 character name of each month. **/
    public static final String[] MONTH_NAMES =
        new String[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
            "Sep", "Oct", "Nov", "Dec"};

    /** Full name of each month. **/
    public static final String[] MONTH_NAMES_FULL =
        new String[]{"January", "February", "March", "April", "May", "June",
            "July", "August", "September", "October", "November", "December"};



    private static int timezoneOffset;

    /**
     * Return the number of days in month (zero based) of year (minus 1900).
     */
    public static int daysInMonth(int year, int month) {
        int ans = MONTH_DAYS[month];
        if (month == 1 && isLeapYear(year + 1900)) {
            return ans + 1;
        }
        return ans;
    }

    /**
     * Is year a leap year?
     */
    public static boolean isLeapYear(int year) {
        return (year % 4) == 0 && (year % 400) != 0;
    }

    /**
     * Get the Monday for the week containing date (first day of week is
     * Monday).
     */
    public static Date getMonday(Date date) {
        Date ans = new Date(date.getTime());
        setTime(ans, 0);
        int day = (ans.getDay() + 6) % 7; // mon=0 tues=1 .. sun=6
        if (day != 0) {
            ans = addDays(ans, -day);
        }
        return ans;
    }

    /**
     * Return a date set to the first day of the month of date.
     */
    public static Date startOfMonth(Date date) {
        Date ans = new Date(date.getYear(), date.getMonth(), 1);
        setTime(ans, 0);
        return ans;
    }

    /**
     * Return a date set to the last day of the month of date.
     */
    public static Date endOfMonth(Date date) {
        int y = date.getYear();
        int m = date.getMonth();
        Date ans = new Date(y, m, daysInMonth(y, m));
        setTime(ans, 2359);
        return ans;
    }

    /**
     * Return a date set to the first day of the year of date.
     */
    public static Date startOfYear(Date date){
        int y = date.getYear();
        return new Date(y, 0, 1);
    }

    /**
     * Return a date set to the first minute of the hour of date.
     */
    public static Date startOfHour(Date date){
        int y = date.getYear();
        int m = date.getMonth();
        int d = date.getDate();
        return new Date(y, m, d, date.getHours(), 0);
    }

    /**
     * Return a date set to the last minute of the hour of date.
     */
    public static Date endOfHour(Date date){
        int y = date.getYear();
        int m = date.getMonth();
        int d = date.getDate();
        return new Date(y, m, d, date.getHours(), 59);
    }

    /**
     * Return a date set to the last second of the last minute of the last
     * hour of date i.e. 23:59:59.
     */
    public static Date endOfDay(Date date){
        int y = date.getYear();
        int m = date.getMonth();
        int d = date.getDate();
        return new Date(y, m, d, 23, 59, 59);
    }

    /**
     * Return a date set to the last day of the year of date.
     */
    public static Date endOfYear(Date date){
        int y = date.getYear();
        return new Date(y, 11, 31);
    }

    /**
     * Return date advanced by months (goes back if neg).
     */
    public static Date addMonths(Date date, int months) {
	    if(months < 0){ // we are subtracting
		    int m = date.getMonth()-1;
		    int y = m<0?date.getYear()-1:date.getYear();
		    m = m<0?11:m;
            int d = Math.min(date.getDay(), DateUtil.daysInMonth(y,m));
		    Date tempDate = new Date(y,m,d);
		    return addMonths(tempDate,months+1);
	    }else if(months > 0){ // we are adding
		    int m = date.getMonth()+1;
		    int y =m>11?date.getYear()+1:date.getYear();
		    m = m>11?0:m;
            int d = Math.min(date.getDay(), DateUtil.daysInMonth(y,m));
		    Date tempDate = new Date(y,m,d);
		    return addMonths(tempDate,months-1);

	    }else{
		    return date;
	    }
    }

    /**
     * Return date advanced by years (goes back if neg).
     */
    public static Date addYears(Date date, int years) {
        int m = date.getMonth();
        int y = date.getYear() + years;
        int d = date.getDay();
        return new Date(y,m,d);
    }

    /**
     * Return date advanced by days (goes back if neg).
     */
    public static Date addDays(Date date, int days) {
        return new Date(date.getTime() + DAY_MS * days);
    }

    /**
     * Convert date to days since 1970.
     */
    public static int days(Date date) {
        return (int)(date.getTime() / DAY_MS);
    }

    /**
     * Return the number of days between startDate and endDate. If startDate
     * and endDate are less than 24h apart then 0 is returned.
     */
    public static int daysBetween(Date startDate, Date endDate) {
        int a = days(endDate);
        int b = days(startDate);
        return a - b;
    }

    /**
     * This method will set the time on a date, time will be somthing like this
     * 1723, this would be 23 mins past five o'clock in the afternoon. 1 is one
     * minute past midnight and 2359 is eleven fifty nine in the evening.
     */
    public static void setTime(Date date, int time) {
        if (date == null) {
            return;
        }
        int hour = time / 100;
        int min = time % 100;
        if (hour < 0 || hour > 23 || min > 59) {
            throw new IllegalArgumentException("Invalid time, " +
                    "must be between 0 and 2359");
        }
        date.setHours(hour);
        date.setMinutes(min);
        date.setSeconds(0);
    }

    public static int weekDays(Date startDate, Date endDate) {
        int daysBetween = daysBetween(startDate,endDate)+1;
        int startDOW = startDate.getDay()+1;
        int endDOW = endDate.getDay()+1;
        int weekDays = ((daysBetween/7)*5);
        int remainingWeekdays = endDOW - startDOW + 1 + (startDOW == 1 ? -1 : 0) + (endDOW == 7 ? -1 : 0);
        if(startDOW == 1 && endDOW == 7) remainingWeekdays = 0; // exception to rule.
        if (remainingWeekdays < 0) remainingWeekdays += 5;
        return weekDays + remainingWeekdays;
    }

    /**
     * Get a date that should be way before any others. Returns the Date representation of 1st Jan 1900
     */
    public static Date getEarlyDate(){
        return new Date(0,0,1);
    }

    /**
     * Get a date that should be way after any others. Returns the Date representation of 31st Dec 2100
     */
    public static Date getLateDate(){
        return new Date(200,11,31);
    }

    /**
     * Returns a meaningful representation of the Year out of a date.
     */
    public static int getYear(Date date){
        return date.getYear() + 1900;
    }

     /**
     * Returns a meaningful representation of the Month out of a date.
     */
    public static int getMonth(Date date){
         return date.getMonth();
    }

    /**
     * Returns a meaningful representation of the Day of Month out of a date.
     */
    public static int getDayOfMonth(Date date){
         return date.getDate();
    }

    /**
     * Return date in format "5 Jan", "12 Dec" etc.
     */
    public static String formatDateAndMonth(Date d) {
        return d.getDate() + " " + MONTH_NAMES[d.getMonth()];
    }

    /**
     * Get the day of the week (0=mon, 1=tues, .., 6=sun).
     */
    public static int getDayOfWeek(Date d) {
        return (d.getDay() + 6) % 7;
    }

    /**
     * Calculate the timezone difference between the client and the server.
     */
    public static void calcTimezoneOffset(long startOfTodayOnServer) {
        Date d = new Date();
        timezoneOffset = (int)(new Date(d.getYear(), d.getMonth(), d.getDate(),
                0, 0).getTime() - startOfTodayOnServer);
    }

    /**
     * Number of ms that need to be added to dates from the server to display
     * the same date and time on the client. Example the date "Mon 2 Feb 00h00"
     * on the server (SA, GMT+2) will be displayed as "Sun 1 Feb 22h00" in
     * on the client (London, GMT+0). If you want the same text to be displayed
     * then add timezoneOffset to the date on client first.
     */
    public static int getTimezoneOffset() {
        return timezoneOffset;
    }

    /**
     * Adjust a date received from the server so that it will display the
     * same text on the client. Example the date "Mon 2 Feb 00h00"
     * on the server (SA, GMT+2) will be displayed as "Sun 1 Feb 22h00"
     * (London, GMT+0) on the client. If you want the same text to be
     * displayed then use this method. Returns null if serverDate is null.
     */
    public static Date serverDateToClient(Date serverDate) {
        if (serverDate == null) {
            return null;
        }
        return new Date(serverDate.getTime() + timezoneOffset);
    }

    /**
     * Adjust a date created on the client so that it will display the
     * same text on the server. Example the date "Sun 1 Feb 22h00"
     * on the client (London, GMT+0) will be displayed as "Mon 2 Feb 02h00"
     * on the server (SA, GMT+2). If you want the same text to be displayed
     * then use this method. Returns null if clientDate is null.
     */
    public static Date clientDateToServer(Date clientDate) {
        if (clientDate == null) {
            return null;
        }
        return new Date(clientDate.getTime() -timezoneOffset);
    }
    
    /**
     * Format date nicely. Cannot use java.text stuff.
     */
    public static String format(Date date) {
        return format(date, true, true, false, false);
    }

    public static String format(Date date, boolean dayName, boolean year,
            boolean time, boolean seconds) {
        return format(date, dayName, year, time, seconds, ' ');
    }

    public static String format(Date date, boolean dayName, boolean year,
            boolean time, boolean seconds, char sep) {
        if (date == null) {
            return ClientUtil.NONE;
        }
        StringBuffer s = new StringBuffer();
        int i;
        if (dayName) {
            s.append(DateUtil.DAY_NAMES[date.getDay()]);
            s.append(sep);
        }
        i = date.getDate();
        if (i < 10) {
            s.append('0');
        }
        s.append(i);
        s.append(sep);
        s.append(DateUtil.MONTH_NAMES[date.getMonth()]);
        if (year) {
            s.append(sep);
            i = date.getYear() % 100;
            if (i < 10) {
                s.append('0');
            }
            s.append(i);
        }
        if (time) {
            s.append(sep);
            i = date.getHours();
            if (i < 10) {
                s.append('0');
            }
            s.append(i);
            s.append(':');
            i = date.getMinutes();
            if (i < 10) {
                s.append('0');
            }
            s.append(i);
            if (seconds) {
                s.append(':');
                i = date.getSeconds();
                if (i < 10) {
                    s.append('0');
                }
                s.append(i);
            }
        }
        return s.toString();
    }

    /**
     * Format date for sorting (i.e. yyyy/mm/dd). Cannot use java.text stuff.
     */
    public static String formatForSort(Date date) {
        if (date == null) {
            return ClientUtil.NONE;
        }
        StringBuffer s = new StringBuffer();
        s.append(date.getYear() + 1900);
        s.append('/');
        int i = date.getMonth() + 1;
        if (i < 10) {
            s.append('0');
        }
        s.append(i);
        s.append('/');
        i = date.getDate();
        if (i < 10) {
            s.append('0');
        }
        s.append(i);
        return s.toString();
    }
}
